/*=====================================================================
  This file is part of a Microsoft SQL Server Shared Source Application.
  Copyright (C) Microsoft Corporation.  All rights reserved.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================= */

using System;
using System.IO;
using System.Collections;
using System.Text;
using System.Xml.Serialization;

namespace Microsoft.SqlServer.BISystems.Tool
{    
    /// <summary>
    /// This class defines what a Token is. 
    /// </summary>
    [Serializable]
    public class Token
    {
        protected string m_name;

        [XmlElement(ElementName = "Name")]
        public string Name
        {
            get 
            {
                return m_name; 
            }
            set 
            {
                try
                {
                    m_name = value;
                }
                catch (Exception e)
                {
                    throw new Exception("Error when deserializing XML tag \"Name\" with value: " + value, e);
                }
            }
        }
        
        protected int m_minQuantity;

        [XmlElement(DataType = "int", ElementName = "MinQuantity")]
        public int MinQuantity
        {
            get 
            {
                return m_minQuantity; 
            }
            set 
            {
                try
                {
                    m_minQuantity = value;
                }
                catch (Exception e)
                {
                    throw new Exception("Error when deserializing XML tag \"MinQuantity\" with value: " + value, e);
                }
            }
        }
        
        protected int m_maxQuantity;

        [XmlElement(DataType = "int", ElementName = "MaxQuantity")]
        public int MaxQuantity
        {
            get 
            {
                return m_maxQuantity; 
            }
            set 
            {
                try
                {
                    m_maxQuantity = value;
                }
                catch (Exception e)
                {
                    throw new Exception("Error when deserializing XML tag \"MaxQuantity\" with value: " + value, e);
                }
            }
        }
                
        protected TokenKey m_tuple;

        [XmlElement(ElementName = "Tuple")]
        public TokenKey Tuple
        {
            get 
            {
                return m_tuple;
            }
            set
            {
                try
                {
                    m_tuple = value;
                }
                catch (Exception e)
                {
                    throw new Exception("Error when deserializing XML tag \"Tuple\" with value: " + value, e);
                }
            }
        }

        private string m_RandomlySelectedSet;

        /// <summary>
        /// Clear the randomly-selected set.
        /// It is created lazily.
        /// </summary>
        public void ClearSelectedMemberSet()
        {
            m_RandomlySelectedSet = null;
        }

        /// <summary>
        /// Get the randomly-selected set.
        /// </summary>
        public string GetSelectedMemberSet()
        {
            return m_RandomlySelectedSet;
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public Token()
        {            
        }

        /// <summary>
        /// Return a random set of tuples, to for this token.
        /// For an entire user output file, all occurrences of the same template parameter 
        /// should have the same randomly-selected tuple values.
        /// </summary>
        /// <param name="server"></param>
        /// <param name="db"></param>
        /// <param name="cube"></param>
        /// <returns></returns>
        public string GenerateSet(ASQueryGenerator asquerygen, string server, string cube, string db, System.Random rand)
        {
            try
            {
                // Did we already generate it?
                if (m_RandomlySelectedSet != null)
                    return m_RandomlySelectedSet;

                // Is information about TokenKey specified for this Token? 
                if (m_tuple == null)
                    throw new Exception("Tuple information is not given for Token: " + m_name);

                // Is TokenKey name specified? It can not null or empty. 
                if (m_tuple.Name == null || m_tuple.Name == "")
                    throw new Exception("Tuple Set is not specified for Token: " + m_name);
                
                // Does the file (source) exist? 
                if (m_tuple.Source != null && m_tuple.Source != "" && !File.Exists(m_tuple.Source))
                {
                    StringBuilder errMsg = new StringBuilder();
                    errMsg.AppendLine("The specified file name does not exist for Token: " + m_name);
                    errMsg.AppendLine("<Token Set=\"" + m_tuple.Name + "\" File=\"" + m_tuple.Source + "\" />");
                    throw new Exception(errMsg.ToString());
                }

                // We retrieve all candidate Tuples for this token.
                // The TupleDictionary maps from key=parameter name, to array of members (tuples).
                string[] values = TupleDictionary.RetrieveTuples(asquerygen, server, cube, db, this, rand);

                // MinQuantity should be less than or equal to MaxQuantity
                // Otherwise, rand.Next will fail. 
                if (m_minQuantity > m_maxQuantity)
                {
                    StringBuilder errMsg = new StringBuilder();
                    errMsg.AppendLine("MinQuantity can not be greater than MaxQuantity.");
                    errMsg.AppendLine("Please check token \"" + m_name + "\"");

                    throw new Exception(errMsg.ToString());
                }

                // MinQuantity must be greater than zero. 
                if (m_minQuantity <= 0)
                {
                    StringBuilder errMsg = new StringBuilder();
                    errMsg.AppendLine("MinQuantity must be greater than zero.");
                    errMsg.AppendLine("Please check token \"" + m_name + "\"");

                    throw new Exception(errMsg.ToString());
                }

                // There are less candidates than we expect. 
                if (m_maxQuantity > values.Length)
                {
                    StringBuilder errMsg = new StringBuilder();
                    errMsg.AppendLine("MaxQuantity is greater than the number of candidate Tuples.");
                    errMsg.AppendLine("MaxQuantity is: " + MaxQuantity.ToString());
                    errMsg.AppendLine("The number of candidate tuples is: " + values.Length.ToString());
                    errMsg.AppendLine("Please decrease MaxQuantity or ensure there are enough number of candidate tuples for this Token: \"" + m_name + "\"");

                    throw new Exception(errMsg.ToString());
                }

                // Generate a random Set from the candidate Tuples.

                // First, generate a random number as the number of Tuples in the Set.
                // Random.Next(minValue, maxValue) treats maxValue as an EXCLUSIVE upper bound,
                // thus m_maxQuantity is incremented by 1 before passing in. 
                int quantity = rand.Next(m_minQuantity, m_maxQuantity + 1);
                
                // Then, randomly pick up Tuples for the candidate. 
                // Note that there is a better algorithm for this, see:
                // Jon Bentley, Bob Floyd, Programming pearls: a sample of brilliance,
                // Communications of the ACM, Volume 30, Issue 9 (September 1987)

                string value = null;

                if (quantity == 1)
                {
                    int id = rand.Next(values.Length);
                    value = values[id];
                }
                else
                {
                    // Initialize a set of flags. They indicate if a value has been selected or not.
                    bool[] flags = new bool[values.Length];
                    // Reset to false. 
                    for (int i = 0; i < values.Length; i++)
                        flags[i] = false;

                    for (int i = 0; i < quantity; i++)
                    {
                        // Randomly pick a member in values array.
                        int id = rand.Next(values.Length);

                        // Check if this member has been used already?
                        while (flags[id] == true)
                        {
                            id = rand.Next(values.Length);
                        }

                        // Now mark the member has been selected. 
                        flags[id] = true;

                        value += values[id] + ",";
                    }

                    // Trim "," that occurs in the end. 
                    value = value.TrimEnd(',');
                }

                // Cache for later.
                // Within a file stream, the same value is always used.
                m_RandomlySelectedSet = value;
                return value;
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
    
    /// <summary>
    /// This class defines a collection of Token. 
    /// So we can index Token by Name. 
    /// </summary>
    [Serializable]
    public class Tokens
    {
        private Token [] m_tokens;

        [XmlElement(ElementName = "Token")]
        public Token[] All
        {
            get
            {
                return m_tokens;
            }
            set
            {
                try
                {
                    m_tokens = value;
                }
                catch (Exception e)
                {
                    throw new Exception("Error when deserializing XML tag \"Token\"", e);
                }
            }
        }

        /// <summary>
        /// Indexer
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Token this[string name]
        {
            get
            {
                // Note that linear search is slow, but this is a search for parameter names,
                // and typically there are only tens not hundreds of them.
                foreach (Token tk in m_tokens)
                {
                    // Token name is case-insensitive.
                    if (String.Compare(tk.Name, name, true) == 0)
                        return tk;
                }

                // If no token is found by the given name, return NULL.
                return null;
            }
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public Tokens()
        {
        }

        /// <summary>
        /// Clear the randomly-selected sets of tuples.
        /// 
        /// This is done before an output file is created,
        /// so each file will have the same tuple set.
        /// For example within the same file, the token |city| will have the same value.
        /// But for the next file it is likely to be different.
        /// 
        /// We also clear the base set of members if it is a dependent definition.
        /// For example:
        ///   Token Name = time_fiscal_month
        ///   Set="TOPCOUNT( [Time].[Fiscal].[Fiscal Week].members, 1000, ( [Measures].[Sale Amt], |time_month| ) )"
        /// This means for every file, the definition of |time_fiscal_month| is re-created using an MDX expression,
        /// which depends upon |time_month|.
        /// </summary>
        public void ClearSelectedMemberSets()
        {
            foreach (Token tk in m_tokens)
            {
                tk.ClearSelectedMemberSet();
                TupleDictionary.ClearTupleSetForDependencies(tk);
            }
        }

        /// <summary>
        /// Output values as a string.
        /// </summary>
        public string OutputXMLValuesAsString()
        {
            StringBuilder str = new StringBuilder(m_tokens.Length * 60);
            foreach (Token tk in m_tokens)
            {
                if (tk.GetSelectedMemberSet() != null)
                {
                    str.Append(tk.Name);
                    str.Append('|');
                    str.Append(tk.GetSelectedMemberSet());
                    str.Append('|');
                }
            }
            return str.ToString();
        }

        /// <summary>
        /// Output the values to the xml stream.
        /// </summary>
        public void OutputXMLValues(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("GeneratedValues");
            foreach (Token tk in m_tokens)
            {
                if (tk.GetSelectedMemberSet() != null)
                {
                    //writer.WriteStartElement("GeneratedValue");
                    writer.WriteElementString(tk.Name, tk.GetSelectedMemberSet());
                    //writer.WriteEndElement();
                }
            }
            writer.WriteEndElement();
        }

    }
}